#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include "arch.h"

#define E_OUT_OF_MEMORY -3
#define E_FILE_OPEN -2

#define SIZEOFCHAR 8
#define MAXTREESTRSIZE 560
#define OPENBRACKET 1
#define CLOSEBRECKET 0
#define LEFTNODE 0
#define RIGHTNODE 1
#define MAXSYMBNUMBER 256
#define EMPTY_NODE 0
#define END_OF_FILE 0

typedef
struct huff_symb_cod{/*struct for array of codef for every char*/
    char * arch_cod;/*cod of archivated symbol*/
    int byte_length;/*strlen =)*/
    int count_of_shift;/*count of significant bites at the last byte*/
}
huffcode;
 /*This function create neded char
	input paraetres:
		byte - byte that will be nulled
		numb_of_empty_bits - count of bites, that will be nulled
	result:	
		needed char
*/
char nulling_empty_bits_of_char_from_left(unsigned char byte, char numb_of_empty_bits){
    char tmp = 0;
	if (numb_of_empty_bits == 0){
		tmp = (char)0;
		tmp --;
	}
	else{
		tmp = 1 << (SIZEOFCHAR- numb_of_empty_bits);
		tmp --;
	}
    byte = (byte >> numb_of_empty_bits) & tmp;
    return byte;
}
/*This function create tree string (full tree code)
	input paraetres:
		root - node, in which we now 
		tmp - char, that contain remained bites
		num - count of significant bites
	result:	
		number of remained bites	
		
*/
unsigned char creating_bin_tree_string (tree * root,FILE * output, unsigned char * tmp, unsigned char num){
    unsigned char bracket = 0;

    if (root == NULL) {
        return num;
    }
    /*There write open bracket before root*/
    num++;
    bracket = OPENBRACKET;
    bracket <<= (SIZEOFCHAR- num);
    *tmp |= bracket;
    /*pinting of char if it's full*/
    if (num == SIZEOFCHAR) {
        fprintf(output,"%c",*tmp);
        *tmp = (char) 0;
        num = (char) 0;
    }
    /*creating new remained bites char*/
    *tmp |= nulling_empty_bits_of_char_from_left((root->data),num);
    fprintf(output,"%c",*tmp);
    *tmp = ((root->data) << (SIZEOFCHAR- num));
    /*print left and right nodes*/
    num = creating_bin_tree_string ( root->left, output, tmp, num); 
    num = creating_bin_tree_string ( root->right, output, tmp, num);
    /*printf close bracket*/
    num++;
    bracket = CLOSEBRECKET;
    bracket <<= (SIZEOFCHAR- num);
    *tmp |= bracket; 
    /*printf char to the file if it's full*/
    if (num == SIZEOFCHAR) {
        fprintf(output,"%c",*tmp);
        *tmp = (char) 0;
        num = (char) 0;
    }

    return num;
}
/*This function call function, which create tree string (full tree code) and printf it to the file
	input paraetres:
		output - file, in which tree will be printed
		root - root of tree 
	result:	
		nothing	
		
*/
void writeTree (FILE * output, tree * root) {
    unsigned char num = 0;
    unsigned char tmp = 0;

    num = creating_bin_tree_string(root, output,&tmp, num);
    if (num != 0){
        fprintf(output,"%c",tmp);
    }

    return;
}
/*This function check recoursive all tree nodes and create array of symbols codes
	input paraetres:
		root - oot of tree
		code - code of way to nownode
		shift_num - count of significant bites 
		length_of_string - length of string, that contain nowcode
		hufcodes_array - array of symbole codes
	result:	
		error number	
*/
int creating_huff_code(tree * root, unsigned char code[], unsigned char shift_num, int length_of_string, huffcode * hufcodes_array[MAXSYMBNUMBER]){
    unsigned char adding_way = 0;
    char tmp = 0;
    int err = 0;
    /*creating new hufcodes_array element from finded data*/
    tmp = code[length_of_string-1];
    if ((root -> data) != EMPTY_NODE){
        (hufcodes_array[(root -> data)]) = (huffcode*) calloc (1, sizeof(huffcode));
        (hufcodes_array[(root -> data)] -> arch_cod) = (char*) calloc (length_of_string, sizeof(char));
        if (!(hufcodes_array[(int)(root -> data)] -> arch_cod)){
            return E_OUT_OF_MEMORY;
        }
        strncpy (hufcodes_array[(int)(root -> data)] -> arch_cod, (char *) code, length_of_string);
        hufcodes_array[(int)(root -> data)] -> count_of_shift = shift_num;
        hufcodes_array[(int)(root -> data)] -> byte_length = length_of_string;
    }
    else{
        if ((root -> left) == NULL){
            (hufcodes_array[0]) = (huffcode*) calloc (1, sizeof(huffcode));
            (hufcodes_array[0] -> arch_cod) = (char*) calloc (length_of_string, sizeof(char));
            if (!(hufcodes_array[0] -> arch_cod)){
                return E_OUT_OF_MEMORY;
            }
            strncpy (hufcodes_array[0] -> arch_cod, (char *) code, length_of_string);
            hufcodes_array[0] -> count_of_shift = shift_num;
            hufcodes_array[0] -> byte_length = length_of_string;
            return err;
        }
        /* adding to string code of going to left and right nodes */
        if (shift_num == 8){
            length_of_string ++;
            shift_num = 0;
            tmp = code[length_of_string-1];
        }
        shift_num++;
        adding_way = LEFTNODE <<(SIZEOFCHAR - shift_num);
        code[length_of_string-1] |=  adding_way;
        err = creating_huff_code(root -> left, code, shift_num, length_of_string, hufcodes_array);
        if (err == E_OUT_OF_MEMORY){
            return err;
        }
        adding_way = RIGHTNODE <<(SIZEOFCHAR - shift_num);
        code[length_of_string-1] |=  adding_way;
        err = creating_huff_code(root -> right, code, shift_num, length_of_string, hufcodes_array);
        if (err == E_OUT_OF_MEMORY){
            return err;
        }
        code[length_of_string-1] =  tmp;
    }   
    return err;     
}
/*This function read symbol from input file, create code of it and write it to the output file
	input paraetres:
		input - input file 
		output - output file
		n - count of significant bytes
		rest - char, that contain rest fro previous codechar
		hufcodes_array - array of codes
	result:	
		error code
*/
int creating_char_code (FILE * input, FILE * output, unsigned char n, unsigned char rest, huffcode * hufcodes_array[MAXSYMBNUMBER]){
    unsigned char data = 0;
    int i = 0;
    char tmp = 0;
    int huffcode_string_length = 0;
    /*print rest and count of sugnificant bites if file is over*/
    while (fscanf(input,"%c",&data) == 1){
        /*printf("%c",data);*/
        tmp = 0;
        huffcode_string_length = hufcodes_array[(int)(data)] -> byte_length;
        /*create char from rest from previous symbol code and first charcode of new symbol*/
        if (huffcode_string_length == 1){
            tmp = rest | nulling_empty_bits_of_char_from_left(hufcodes_array[(int)(data)] -> arch_cod[0],n);
            if (((hufcodes_array[(int)(data)] -> count_of_shift) + n) >= SIZEOFCHAR){
                fprintf(output,"%c",tmp);
                rest = 	(hufcodes_array[(int)(data)] -> arch_cod[0]) << (SIZEOFCHAR - n);
                n = (hufcodes_array[(int)(data)] -> count_of_shift) - (SIZEOFCHAR - n);
            }
            else{
                rest = tmp;
                n += (hufcodes_array[(int)(data)] -> count_of_shift);
            }
        }
        else{	
            tmp = rest | nulling_empty_bits_of_char_from_left(hufcodes_array[(int)(data)] -> arch_cod[0],n);
            fprintf(output,"%c",tmp);
            /*create char from rest from previous charcode and nowcharcode*/
            for (i = 1; i < huffcode_string_length - 1; i++){
                tmp = (hufcodes_array[(int)(data)] -> arch_cod[i-1]) << (8-n) | nulling_empty_bits_of_char_from_left(hufcodes_array[(int)(data)] -> arch_cod[i],n);
                fprintf(output,"%c",tmp);
            }
            /*create the rest fro last charcode*/
            if (((hufcodes_array[(int)(data)] -> count_of_shift) + n) >= 8){
                tmp = (hufcodes_array[(int)(data)] -> arch_cod[huffcode_string_length-2]) << (8-n) | nulling_empty_bits_of_char_from_left(hufcodes_array[(int)(data)] -> arch_cod[huffcode_string_length-1],n);
                fprintf(output,"%c",tmp);/*print code if it's full and create rest from last bites*/
                rest = (hufcodes_array[(int)(data)] -> arch_cod[huffcode_string_length-1]) << (8-n);
                n = ((hufcodes_array[(int)(data)] -> count_of_shift) + n) - 8;
            }
            else{
                rest = (hufcodes_array[(int)(data)] -> arch_cod[huffcode_string_length-2]) << (8-n) | nulling_empty_bits_of_char_from_left(hufcodes_array[(int)(data)] -> arch_cod[huffcode_string_length-1],n);
                n += (hufcodes_array[(int)(data)] -> count_of_shift);
            }
        }
    }
    if (n !=0){
        fprintf(output,"%c%c",rest,n);
    }
    else{
        n = 8;
        fprintf(output,"%c",n);
    }
    fclose (input);
    fclose (output);
    return END_OF_FILE;
}
/*This write filedata to the 
	input paraetres:
		input - input file 
		output - output file
	result:	
		error code
*/
int write_data (FILE * input, FILE * output, tree * root){
    int err = 0;
    huffcode * hufcodes_array[MAXSYMBNUMBER] = {NULL};
    unsigned char code[4] = {0};
    unsigned char shift_num = (char)0;
    unsigned char rest = 0;
    int length_of_string = 1;

    err = creating_huff_code (root, code, shift_num, length_of_string, (hufcodes_array));

    if (err == E_OUT_OF_MEMORY){
        return err;
    }
    err = creating_char_code(input, output, shift_num, rest, hufcodes_array);

    return err;
}

/*This function open files and call treewrite and datawrite function
	input paraetres:
		input_file - name of input file 
	result:	
		error code
*/
int archivate (char * input_file, char * output_file) {
    FILE * input = fopen (input_file, "rb");
    FILE * output = NULL;
    int error = 0;
    tree * root = NULL;
     
    if (! input) { /* check file open*/
        return E_FILE_OPEN;
    }
    
    output = fopen (output_file, "wb");
    if (! output) { /* check file open*/
        return E_FILE_OPEN;
    }
    
    if ((error = binTreeBuilder (input, &root)) != 0) { /* create bin tree*/
        return error;
    }

    writeTree (output, root);
    error = write_data (input, output, root);
    if (error == E_OUT_OF_MEMORY){
        return error;
    }

    return error;
}
